package com.platform.zuul.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.apache.exception.BusinessException;
import com.apache.passport.common.PassportHelper;
import com.apache.passport.common.XmlWhiteUtils;
import com.apache.tools.ConfigUtil;
import com.apache.tools.DateUtils;
import com.apache.tools.StrUtil;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URL;
import java.util.*;
import java.util.concurrent.atomic.LongAdder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * description: 限流过滤器
 *
 * @author dyh 创建时间：2018年03月19日
 */
@Component
public class RateLimitZuulFilter extends ReqPermissionFilter {
    Logger log = Logger.getLogger(getClass());
    private Map<String, RateLimiter> rateLimit = Maps.newConcurrentMap();
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // 这边的order一定要大于org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter的order
        // 也就是要大于5
        // 否则，RequestContext.getCurrentContext()里拿不到serviceId等数据。
        //return Ordered.LOWEST_PRECEDENCE;
        return 9;
    }

    @Override
    public boolean shouldFilter() {
        // 这里可以考虑弄个限流开启的开关，开启限流返回true，关闭限流返回false
        return true;
    }

    @Override
    public Object run() {
        try {
            RequestContext currentContext = RequestContext.getCurrentContext();
            HttpServletResponse response = currentContext.getResponse();
            HttpServletRequest request = currentContext.getRequest();
//            currentContext.setRequest(new SecurityHttpServletRequest(request));
//            currentContext.setResponse(new SecurityHttpServletResponse(response));
            String isOpenSecurity = StrUtil.doNull(ConfigUtil.getInstance().getValueByKey("is_open_security_filter"),"true");
            boolean mark = cleanXSS(request, isOpenSecurity);
            if (mark) {
                setUnauthorizedResponse(currentContext);
            }
            log.debug("sessionId="+request.getSession().getId());
            String uri = request.getRequestURI();//获取uri信息
            String suffix = PassportHelper.getInstance().parseUrlSuffix(uri);//获取后缀名
            if (StrUtil.isNotNull(suffix)) {//如果存在后缀为图片,css等格式,直接跳过,不拦截
                suffix = suffix.toLowerCase();//后缀名小写
                if (XmlWhiteUtils.SUFFIX.contains(suffix)) {
                    return null;
                }
            }
            if(uri.indexOf("/uniteAuth/randCode") !=-1){
                return null;
            }
            //以下黑白名单处理逻辑
            String ip = getIpAddress(request,false);
            JSONObject zuulBlack = routeLocator.getRoute("black.white",ip);
            if("0".equals(zuulBlack.getString("infoType"))){//黑名单
                throw new BusinessException("系统拒绝访问，您的操作权限受限");
            }
            // StackTraceElement stack[] = Thread.currentThread().getStackTrace();
            String key = String.valueOf(RequestContext.getCurrentContext().get("serviceId"));
            if (StringUtils.isEmpty(key) || "null".equalsIgnoreCase(key)) {
                URL routeHost = RequestContext.getCurrentContext().getRouteHost();
                if (routeHost != null) {
                    key = routeHost.toString();
                }
            }
            JSONObject route = routeLocator.getRoute("", key);
            if("1".equalsIgnoreCase(route.getString("ifHystrix"))){//开启了熔断
                long dd = Double.valueOf(StrUtil.doNull(route.getString("hstxCondition"), "1000")).longValue();
                String date = DateUtils.Now.fmt_yyyyMMdd();
                if (!hystrix.containsKey(uri)) {
                    Map<String, LongAdder> limit = new HashMap();
                    limit.put(date, new LongAdder());
                    hystrix.put(uri, limit);
                }
                Map<String, LongAdder> limit = hystrix.get(uri);
                boolean tryAcquire = true;
                if (limit.containsKey(date)) {
                    LongAdder lad = limit.get(date);
                    if (dd >= lad.sum()) {
                        lad.increment();
                        tryAcquire = false;
                    }
                } else {
                    limit.clear();
                    limit.put(date, new LongAdder());
                    tryAcquire = false;
                }
                if (tryAcquire) {//达到熔断条件
                    throw new BusinessException("该服务已达到服务请求最大上限，已暂停该服务");
                }
            }
            if(StrUtil.isNotNull(route.getString("rateLimit"))){//限流判断
                double limit = Long.valueOf(route.getString("rateLimit")) * 1.0;
                if (!rateLimit.containsKey(key)) {
                    rateLimit.put(key, RateLimiter.create(limit));//每秒不超过1000个任务被提交
                }
                RateLimiter rateLimiter = rateLimit.get(key);
                if (!rateLimiter.tryAcquire()) {
                    HttpStatus httpStatus = HttpStatus.TOO_MANY_REQUESTS;
                    response.setContentType(MediaType.TEXT_PLAIN_VALUE);
                    response.setStatus(httpStatus.value());
                    response.getWriter().append(httpStatus.getReasonPhrase());
                    currentContext.setSendZuulResponse(false);
                    throw new ZuulException(httpStatus.getReasonPhrase(), httpStatus.value(), httpStatus.getReasonPhrase());
                }
            }

            int count = doFilterInvoke(request, isOpenSecurity);
            if(count ==0){
                throw new BusinessException("系统拒绝访问，您的操作权限受限");
            }
            String randCode = routeLocator.getCahceInfo(getIpAddress(request,true));
            if(StrUtil.isNotNull(randCode)) {
                currentContext.addZuulRequestHeader("rand-code", randCode);
            }
            if(key.toLowerCase().indexOf("sso-server")!=-1){
                String tokenId = PassportHelper.getInstance().getCurrCookie(request);
                if(StrUtil.isNotNull(tokenId)){
                    String value = PassportHelper.getInstance().getCurrCookie(request, "_uc.sso");
                    currentContext.addZuulRequestHeader("zuulucsso", value);
                    currentContext.addZuulRequestHeader("zuultokenid", tokenId);
                }
            }else {
                String goUrl = this.runPermission(key);
                if (StrUtil.isNotNull(goUrl)) {
                    response.sendRedirect(goUrl);
                }
            }
        } catch (Exception e) {
            throw new BusinessException(e.getMessage());
           // ReflectionUtils.rethrowRuntimeException(e);
        }
        return null;
    }

    private int doFilterInvoke(HttpServletRequest request, String isOpenSecurity) {
        if(!"true".equalsIgnoreCase(isOpenSecurity)){//没开启安全验证
            return 1;
        }
        String uri = request.getRequestURI();//获取uri信息
        this.log.info("当前请求地址为：" + uri);
        if(uri.indexOf("/api/")==-1){
            return 1;
        }
        String referer = request.getHeader("Referer");
        String serverName = request.getServerName().toLowerCase();
        if("localhost".equals(serverName) || "127.0.0.1".equals(serverName)){
            return 1;
        }
        if(StrUtil.isNull(referer)){
            referer = request.getScheme()+"://"+getIpAddress(request,false);
            //request.getScheme()+"://"+request.getServerName();
        }
        String refererUrl = ConfigUtil.getInstance().getValueByKey("refererUrl");
        if(StrUtil.isNull(refererUrl) || referer.indexOf("://localhost")>-1 || referer.indexOf("://127.0.0.1")>-1){
            return 1;
        }

        String[] strsUrls = refererUrl.split(",");
        log.info("refererUrl="+strsUrls);
        for (int i = 0; i < strsUrls.length; i++) {
            if (referer.startsWith(strsUrls[i])) {
                return 1;
            }
        }
        this.log.info("当前请求来源地址为：" + referer + "，不是合法请求地址来源；安全地址为："+strsUrls);
        return 0;
    }


    private String getIpAddress(HttpServletRequest request,boolean isAll) {
        if(isAll){
            String serverId = PassportHelper.getInstance().getCurrCookie(request,"__guuid");
            log.info("__guuid="+serverId);
            if(StrUtil.isNotNull(serverId)){//以serverID为主
                return serverId;
            }else{
                return request.getRequestedSessionId();
            }
        }
        String ip = request.getHeader("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值，第一个ip才是真实ip
            if (ip.indexOf(",") != -1) {
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    private String[] badStrs = { "<script", "confirm(", "prompt(", "eval(", "function(", "alert(", ":alert",
            "ltrim(", "[window[", "<iframe", "<a href", "<input ", "<img", "<audio", "onerror\\=", "ltrim(",
            "{tostring:", "</script", "</style", "href=","vbscript"};//过滤掉的sql关键字，可以手动添加

    private boolean cleanXSS(HttpServletRequest request, String isOpenSecurity) {
        if(!"true".equalsIgnoreCase(isOpenSecurity)){//没开启安全验证
            return false;
        }
        String pui = request.getRequestURI();
        if (pui.toLowerCase().contains("/owa_util.signature") || pui.toLowerCase().contains("/sqlnet.trc")) {
            log.warn("非法请求参数=" + pui);
            return true;
        }
        if (StrUtil.isNotNull(request.getParameter("formToken"))) {
            return false;
        }
        Map<String, String[]> parameterMap = request.getParameterMap();
        StringBuilder sb = new StringBuilder();
        if(!parameterMap.isEmpty()) {
            Iterator it_d = parameterMap.entrySet().iterator();
            while (it_d.hasNext()) {
                Map.Entry<String, String[]> entry_d = (Map.Entry) it_d.next();
                String key = entry_d.getKey();
                String[] value = entry_d.getValue();
                if (value != null) {
                    List<String> strings = Arrays.asList(value);
                    for (int i = 0; i < strings.size(); i++) {
                        String vstr = strings.get(i);
                        strings.set(i, StringEscapeUtils.escapeHtml(vstr));
                        strings.set(i, StringEscapeUtils.escapeJavaScript(vstr));
                        if(vstr.length() < 200) {
                            sb.append(strings.get(i));
                        }
                    }
                }
                parameterMap.put(key, value);
            }
            String newBody = JSON.toJSONString(parameterMap);
            if (newBody.contains("forbid")) {
                return true;
            }
        }
        sb.append(request.getQueryString());
        String referer = request.getHeader("Referer");
        sb.append(referer);
        String str = sb.toString();
        if (StrUtil.isNull(str))
            return false;
        String sql = str.toLowerCase();
        sql = sql.replace("%28", "(").replace("%2b", "+").replace("%3c", "<").replace("%27", "'").replace("%5b", "[")
                .replace("%5d", "]").replace("%3d", "=").replace("%7c", "|").replace("%7b", "{").replace("%3a", ":")
                .replace("%2f", "/");
        log.info("请求参数=" + sql);
        String security_filter_strs = ConfigUtil.getInstance().getValueByKey("security_filter_strs");
        if(StrUtil.isNotNull(security_filter_strs)){
            badStrs = security_filter_strs.split(",");
        }
        for (int i = 0; i < badStrs.length; i++) {
            if (sql.indexOf(badStrs[i]) >= 0) {
                return true;
            }
        }
        return isEqualString(str);
    }

    private boolean isEqualString(String str) {
        String regEx_style = "(<[a-zA-Z].*?>)|(<[\\/][a-zA-Z].*?>)";
        Pattern p = Pattern.compile(regEx_style);
        Matcher m = p.matcher(str);
        return m.matches();
    }

    /**
     * 设置500拦截状态
     */
    private void setUnauthorizedResponse(RequestContext requestContext) {
        JSONObject obj = new JSONObject();
        obj.put("errorCode","500");
        obj.put("errorMesage","请求参数非法");
        requestContext.setResponseBody(obj.toJSONString());
        throw new BusinessException("请求参数非法");
    }
}
